package com.mobike.android.view

import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.view.TouchDelegate
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.LinearLayout
import android.widget.TextView
import java.util.concurrent.atomic.AtomicInteger


private val sNextGeneratedId = AtomicInteger(1)

fun generateViewId16(): Int {
  while (true) {
    val result = sNextGeneratedId.get()
    // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
    var newValue = result + 1
    if (newValue > 0x00FFFFFF) newValue = 1 // Roll over to 1, not 0.
    if (sNextGeneratedId.compareAndSet(result, newValue)) {
      return result
    }
  }
}

fun generateNewIdCompat(): Int {
  return if (Build.VERSION.SDK_INT >= 17) {
    View.generateViewId()
  } else {
    generateViewId16()
  }
}


inline fun View.setPaddings(a: Int) = this.setPadding(a, a, a, a)

inline var View.leftPadding: Int
  get(): Int = this.paddingLeft
  set(i) = this.setPadding(i, this.paddingTop, this.paddingRight, this.paddingBottom)

inline var View.topPadding: Int
  get(): Int = this.paddingTop
  set(i) = this.setPadding(this.paddingLeft, i, this.paddingRight, this.paddingBottom)

inline var View.rightPadding: Int
  get(): Int = this.paddingRight
  set(i) = this.setPadding(this.paddingLeft, this.paddingTop, i, this.paddingBottom)

inline var View.bottomPadding: Int
  get(): Int = this.paddingBottom
  set(i) = this.setPadding(this.paddingLeft, this.paddingTop, this.paddingRight, i)

inline var View.marginTop: Int
  get(): Int = (this.layoutParams as ViewGroup.MarginLayoutParams).topMargin
  set(i) = run { (this.layoutParams as ViewGroup.MarginLayoutParams).topMargin = i }

inline var View.marginBottom: Int
  get(): Int = (this.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
  set(i) = run { (this.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = i }

inline var View.weight: Float
  get(): Float = (this.layoutParams as LinearLayout.LayoutParams).weight
  set(i) = run { (this.layoutParams as LinearLayout.LayoutParams).weight = i }

inline val View.centerY: Int
  get(): Int = (this.top + this.bottom) / 2

inline val View.centerX: Int
  get(): Int = (this.left + this.right) / 2

inline fun View.indexInParent(): Int {
  val group = this.parent as ViewGroup
  return group.indexOfChild(this)
}


inline val View.paramsRadius: Int
  get() = run {
    val lp = this.layoutParams
    if (lp.width == lp.height) {
      return lp.width / 2
    } else {
      throw Exception("The width and height should be the same!")
    }
  }

inline var View.paramsHeight: Int
  get() = run {
    return this.layoutParams.height
  }
  set(b) = run {
    if (this.layoutParams.height != b) {
      this.layoutParams.height = b
      this.requestLayout()
    }
  }

var View.bothEnabledAndClickable: Boolean
  get() = this.isEnabled && this.isClickable
  set(b: Boolean) = run {
    this.isEnabled = b
    this.isClickable = b
  }

inline val View.paramsWidth: Int
  get() = run {
    return this.layoutParams.width
  }

inline val ViewGroup.children
  get(): Iterator<View> = run {
    return object : Iterator<View> {
      var c = 0
      override fun hasNext(): Boolean = c < childCount

      override fun next(): View {
        val r = getChildAt(c)
        c += 1
        return r
      }
    }
  }

fun View.remove() {
  val p = this.parent as? ViewGroup
  if (p != null) {
    val index = p.indexOfChild(this)
    if (index >= 0) p.removeViewAt(index)
  }
}

inline val View.paramsHalfHeight: Int
  get() = run {
    return this.layoutParams.height / 2
  }

inline val Int.measureMode get() = android.view.View.MeasureSpec.getMode(this)
inline val Int.measureSize get() = android.view.View.MeasureSpec.getSize(this)

inline fun measureSpecAtMost(i: Int) = View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.AT_MOST)
inline fun measureSpecExactly(i: Int) = View.MeasureSpec.makeMeasureSpec(
  i,
  View.MeasureSpec.EXACTLY
)

val WrapContent = ViewGroup.LayoutParams.WRAP_CONTENT
val MatchParent = ViewGroup.LayoutParams.MATCH_PARENT


fun View.forceMeasure(width: Int, height: Int) {
  measure(
    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
    View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
  )
  layout(0, 0, measuredWidth, measuredHeight)
}

fun View.forceMeasureWrapContent() {
  measure(
    View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.AT_MOST),
    View.MeasureSpec.makeMeasureSpec(screenHeight, View.MeasureSpec.AT_MOST)
  )
  layout(0, 0, measuredWidth, measuredHeight)
}


fun View.forEachChild(p: (View) -> Unit) {
  p(this)
  if (this is ViewGroup) {
    for (i in (0 until childCount)) {
      getChildAt(i).forEachChild(p)
    }
  }
}

fun View.findChild(p: (View) -> Boolean): View? {
  if (p(this)) {
    return this
  } else {
    if (this is ViewGroup) {
      for (i in (0 until childCount)) {
        val can = getChildAt(i).findChild(p)
        if (can != null) {
          return can
        }
      }
    }
    return null
  }
}


fun View.relativePositionTo(parent: View): Point {
  val pthis = intArrayOf(0, 0)
  this.getLocationInWindow(pthis)
  val pother = intArrayOf(0, 0)
  parent.getLocationInWindow(pother)
  return Point(pthis[0] - pother[0], pthis[1] - pother[1])
}

fun View.visibleOrGone(b: Boolean) {
  val res = (if (b) View.VISIBLE else View.GONE)
  if (visibility != res) visibility = res
}


fun View.visibleOrInvisible(b: Boolean) {
  val res = (if (b) View.VISIBLE else View.INVISIBLE)
  if (visibility != res) visibility = res
}


fun View.onPreDrawOnce(func: () -> Boolean) {
  val vto = viewTreeObserver
  vto.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
    var called = false
    override fun onPreDraw(): Boolean {
      if (!called) {
        called = true
        if (vto.isAlive) vto.removeOnPreDrawListener(this)
        return func.invoke()
      }
      return true
    }
  })
}


fun TextView.copyTextAttributeFrom(o: TextView) {
  this.textSize = o.textSize
  this.setTextColor(o.currentTextColor)
}

// don't use this, has bug
fun View.enlargeTouchArea(distance: Int = 48.dp) {
  return
  val parent = parent as? ViewGroup
  if (parent?.touchDelegate != null) throw IllegalStateException(
    "parent of this view $this already has a touch delegate"
  )
  parent?.post {
    viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
      override fun onPreDraw(): Boolean {
        val bounds = Rect()
        getHitRect(bounds)
        if (bounds.isEmpty) return true

        bounds.top -= distance
        bounds.left -= distance
        bounds.right += distance
        bounds.bottom += distance

        val delegate = TouchDelegate(bounds, this@enlargeTouchArea)
        parent.touchDelegate = delegate
        viewTreeObserver.removeOnPreDrawListener(this)
        return true
      }

    })

  }
}

fun View.backgroundColor(): Int = (background as? ColorDrawable)?.color!!

